神經網路介紹
林嶔 (Lin, Chin)
– 神經細胞的構造如下,不論是何種神經元皆可分成:接收區、觸發區、傳導區和輸出區。
透過樹突(dendrite)能接收上一個神經元的訊息,而有些會在接收訊息後產生抑制性作用,有些會產生興奮性作用,然後這些訊號再透過神經元整合,之後再透過軸突(axon)將訊號傳導出去。
我們根據這樣的生物學知識,開始來用電腦模擬一個簡單的神經元。
\[ \begin{align} \mbox{weighted sum} & = w_{0} + w_{1}x_1 + w_{2}x_2 + \dots \\ \hat{y} & = step(\mbox{weighted sum}) \end{align} \]
– 讓我們使用MxNet來實現吧!這次我們同樣使用剛剛的IRIS資料集,請到這裡下載這份資料集
– 這次預測的任務比較不一樣,我們使用一部分的資料作為Training set,一部分的資料作為Testing set,而使用花萼(sepal)和花瓣(petal)的長度和寬度來預測鳶尾屬下的三個亞屬:山鳶尾(setosa)、變色鳶尾(versicolor)和維吉尼亞鳶尾(virginica):
iris = read.csv('data/iris.csv')
X.array = array(t(as.matrix(iris[,-5])), dim = c(4, 150))
Y.array = array(t(model.matrix(~ -1 + factor(iris[,5]))), dim = c(3, 150))
set.seed(0)
TRAIN.seq = sample(1:150, 100)
TRAIN.X.array = X.array[,TRAIN.seq]
TRAIN.Y.array = Y.array[,TRAIN.seq]
TEST.X.array = X.array[,-TRAIN.seq]
TEST.Y.array = Y.array[,-TRAIN.seq]
library(mxnet)
data = mx.symbol.Variable(name = 'data')
fc1 = mx.symbol.FullyConnected(data = data, num.hidden = 5, name = 'fc1')
act1 = mx.symbol.Activation(data = fc1, act.type = 'relu', name = 'act1')
fc2 = mx.symbol.FullyConnected(data = act1, num.hidden = 3, name = 'fc2')
out_layer = mx.symbol.SoftmaxOutput(data = fc2, name = 'out_layer')
my.eval.metric.mlogloss <- mx.metric.custom(
name = "m-logloss",
function(real, pred) {
real1 = as.numeric(as.array(real))
pred1 = as.numeric(as.array(pred))
pred1[pred1 <= 1e-6] = 1e-6
pred1[pred1 >= 1 - 1e-6] = 1 - 1e-6
return(-mean(real1 * log(pred1), na.rm = TRUE))
}
)
mx.set.seed(0)
iris_model = mx.model.FeedForward.create(symbol = out_layer,
X = TRAIN.X.array, y = TRAIN.Y.array,
optimizer = "sgd", learning.rate = 0.05, momentum = 0.9,
array.batch.size = 20, num.round = 100,
ctx = mx.cpu(),
eval.metric = my.eval.metric.mlogloss)
predict_Y = predict(iris_model, TEST.X.array, array.layout = "colmajor")
predict_cat = max.col(t(predict_Y))
real_cat = max.col(t(TEST.Y.array))
confusion_table = table(predict_cat, real_cat)
print(confusion_table)
## real_cat
## predict_cat 1 2 3
## 1 18 0 0
## 2 0 13 0
## 3 0 2 17
– 但回到我們的手寫數字分類問題,當我們看到這些手寫數字時,我們一眼就能認出他們了,但從「圖片」到「概念」的過程真的這麼簡單嗎?
– 現在我們面對的是視覺問題,看來除了模擬大腦思考運作的過程之外,我們還需要模擬眼睛的作用!
– 他們的研究發現,貓咪在受到不同形狀的圖像刺激時,感受野的腦部細胞會產生不同反應
– 卷積器模擬了感受野最初的細胞,他們負責用來辨認特定特徵,他們的數學模式如下:
– 「特徵圖」的意義是什麼呢?卷積器就像是最初級的視覺細胞,他們專門辨認某一種簡單特徵,那這個「特徵圖」上面數字越大的,就代表那個地方越符合該細胞所負責的特徵。
獲得特徵圖之後,還記得我們為了增加神經網路的數學複雜性,會添加一些非線性函數做轉換,因此卷積神經網路在經過卷積層後的特徵圖會再經過非線性轉換。
接著,由於連續卷積的特徵圖造成了訊息的重複,這時候我們經常會使用「池化層」(pooling layer)進行圖片降維,事實上他等同於把圖片的解析度調低,這同時也能節省計算量。
– 我們想像有一張人的圖片,假定第一個卷積器是辨認眼睛的特徵,第二個卷積器是在辨認鼻子的特徵,第三個卷積器是在辨認耳朵的特徵,第四個卷積器是在辨認手掌的特徵,第五個卷積器是在辨認手臂的特徵
– 第1.2.3張特徵圖中數值越高的地方,就分別代表眼睛、鼻子、耳朵最有可能在的位置,那將這3張特徵圖合在一起看再一次卷積,是否就能辨認出人臉的位置?
– 第4.5張特徵圖中數值越高的地方,就分別代表手掌、手臂最有可能在的位置,那將這2張特徵圖合在一起看再一次卷積,是否就能辨認出手的位置?
– 第4.5張特徵圖對人臉辨識同樣能起到作用,因為人臉不包含手掌、手臂,因此如果有個卷積器想要辨認人臉,他必須對第1.2.3張特徵圖做正向加權,而對第4.5張特徵圖做負向加權
– 請在這裡下載MNIST的手寫數字資料,並讓我們了解一下這筆資料的結構
library(data.table)
mnist = fread("data/MNIST.csv", data.table = FALSE)
mnist = data.matrix(mnist)
n.sample = dim(mnist)[1]
X = mnist[,-1]
X = t(X)
dim(X) = c(28, 28, 1, dim(mnist)[1])
for (i in 1:dim(X)[4]) {
X[,,,i] = t(X[,,,i] )
}
Y = array(t(model.matrix(~ -1 + factor(mnist[,1]))), dim = c(10, n.sample))
library(OpenImageR)
imageShow(X[,,,25])
Y[,25]
## [1] 0 0 1 0 0 0 0 0 0 0
– 另外,我們需要對X進行標準化(平均值33.4,標準差78.7),有些人可以嘗試不先標準化的結果,你會發現根本無法訓練!
print(mean(X))
## [1] 33.40891
print(sd(X))
## [1] 78.67774
X = (X - mean(X))/sd(X)
set.seed(0)
Train.sample = sample(1:n.sample, n.sample*0.6, replace = FALSE)
Train.X = X[,,,Train.sample]
dim(Train.X) = c(28, 28, 1, n.sample * 0.6)
Train.Y = Y[,Train.sample]
Test.X = X[,,,-Train.sample]
dim(Test.X) = c(28, 28, 1, n.sample * 0.4)
Test.Y = Y[,-Train.sample]
fwrite(x = data.table(mnist[Train.sample,]),
file = 'data/train_data.csv',
col.names = FALSE, row.names = FALSE)
fwrite(x = data.table(mnist[-Train.sample,]),
file = 'data/test_data.csv',
col.names = FALSE, row.names = FALSE)
# input
data <- mx.symbol.Variable('data')
# first conv
conv1 <- mx.symbol.Convolution(data=data, kernel=c(5,5), num_filter=10, name = 'conv1')
relu1 <- mx.symbol.Activation(data=conv1, act_type="relu")
pool1 <- mx.symbol.Pooling(data=relu1, pool_type="max",
kernel=c(2,2), stride=c(2,2))
# second conv
conv2 <- mx.symbol.Convolution(data=pool1, kernel=c(5,5), num_filter=20, name = 'conv2')
relu2 <- mx.symbol.Activation(data=conv2, act_type="relu")
pool2 <- mx.symbol.Pooling(data=relu2, pool_type="max",
kernel=c(2,2), stride=c(2,2))
# first fullc
flatten <- mx.symbol.Flatten(data=pool2)
fc1 <- mx.symbol.FullyConnected(data=flatten, num_hidden=150, name = 'fc1')
relu3 <- mx.symbol.Activation(data=fc1, act_type="relu")
# second fullc
fc2 <- mx.symbol.FullyConnected(data=relu3, num_hidden=10, name = 'fc2')
# Softmax
lenet <- mx.symbol.SoftmaxOutput(data = fc2, name = 'lenet')
原始圖片(28x28x1)要先經過10個5x5的「卷積器」(5x5x1x10)處理,將使圖片變成10張「一階特徵圖」(24x24x10)
接著這10張「一階特徵圖」(24x24x10)會經過ReLU,產生10張「轉換後的一階特徵圖」(24x24x10)
接著這10張「轉換後的一階特徵圖」(24x24x10)再經過2x2「池化器」(2x2)處理,將使圖片變成10張「降維後的一階特徵圖」(12x12x10)
– 第二層卷積組合
再將10張「降維後的一階特徵圖」(12x12x10)經過20個5x5的「卷積器」(5x5x10x20)處理,將使圖片變成20張「二階特徵圖」(8x8x20)
接著這20張「二階特徵圖」(8x8x20)會經過ReLU,產生20張「轉換後的二階特徵圖」(8x8x20)
接著這20張「轉換後的二階特徵圖」(8x8x20)再經過2x2「池化器」(2x2)處理,將使圖片變成20張「降維後的二階特徵圖」(4x4x20)
– 全連接層
將「降維後的二階特徵圖」(4x4x20)重新排列,壓製成「一階高級特徵」(320)
讓「一階高級特徵」(320)進入「隱藏層」,輸出「二階高級特徵」(150)
「二階高級特徵」(150)經過ReLU,輸出「轉換後的二階高級特徵」(150)
「轉換後的二階高級特徵」(150)進入「輸出層」,產生「原始輸出」(10)
「原始輸出」(10)經過Softmax函數轉換,判斷圖片是哪個類別
my.eval.metric.mlogloss <- mx.metric.custom(
name = "m-logloss",
function(real, pred) {
real1 = as.numeric(as.array(real))
pred1 = as.numeric(as.array(pred))
pred1[pred1 <= 1e-6] = 1e-6
pred1[pred1 >= 1 - 1e-6] = 1 - 1e-6
return(-mean(real1 * log(pred1), na.rm = TRUE))
}
)
mx.set.seed(0)
lenet_model = mx.model.FeedForward.create(symbol = lenet,
X = Train.X, y = Train.Y,
optimizer = "sgd", learning.rate = 0.05, momentum = 0.9,
array.batch.size = 100, num.round = 20,
ctx = mx.cpu(),
eval.metric = my.eval.metric.mlogloss)
predict_Y = predict(lenet_model, Test.X)
predict_cat = max.col(t(predict_Y))
real_cat = max.col(t(Test.Y))
confusion_table = table(predict_cat, real_cat)
cat("Testing accuracy rate =", sum(diag(confusion_table))/sum(confusion_table))
## Testing accuracy rate = 0.9795833
print(confusion_table)
## real_cat
## predict_cat 1 2 3 4 5 6 7 8 9 10
## 1 1656 1 4 2 3 4 11 0 8 7
## 2 0 1841 6 0 5 0 1 7 5 1
## 3 0 2 1625 4 1 0 1 15 6 0
## 4 0 0 3 1678 0 2 0 3 4 5
## 5 0 1 5 0 1568 0 7 1 3 7
## 6 1 0 2 42 0 1515 2 0 4 12
## 7 1 0 2 0 0 15 1638 0 18 0
## 8 1 3 5 5 5 8 0 1721 1 14
## 9 2 3 4 7 1 3 1 2 1622 3
## 10 2 0 0 4 23 4 0 4 4 1593
#Save model
mx.model.save(lenet_model, "model/lenet", iteration = 0)
#Load model
lenet_model = mx.model.load("model/lenet", iteration = 0)
– 範例圖片可以從這裡下載
library(OpenImageR)
new_digit = readImage('digits/digit.1.jpeg')
imageShow(new_digit)
new_digit = rgb_2gray(new_digit)
dim(new_digit) = c(28, 28, 1, 1)
pred_y = predict(lenet_model, new_digit)
print(pred_y)
## [,1]
## [1,] 0.004551838
## [2,] 0.009206435
## [3,] 0.008242927
## [4,] 0.014129665
## [5,] 0.002852110
## [6,] 0.010479977
## [7,] 0.007251740
## [8,] 0.004390656
## [9,] 0.932146251
## [10,] 0.006748293
new_digit = new_digit * 255
new_digit = (new_digit - 33.4) / 78.7
dim(new_digit) = c(28, 28, 1, 1)
pred_y = predict(lenet_model, new_digit)
print(round(pred_y, 3))
## [,1]
## [1,] 0
## [2,] 0
## [3,] 0
## [4,] 0
## [5,] 0
## [6,] 0
## [7,] 0
## [8,] 0
## [9,] 1
## [10,] 0
preprocessing = function (img) {
img = rgb_2gray(img)
img = img * 255
img = (img - 33.4) / 78.7
dim(img) = c(28, 28, 1, 1)
img
}
new_digit = readImage('digits/digit.2.jpeg')
imageShow(new_digit)
new_digit = preprocessing(new_digit)
pred_y = predict(lenet_model, new_digit)
print(round(pred_y, 3))
## [,1]
## [1,] 0
## [2,] 1
## [3,] 0
## [4,] 0
## [5,] 0
## [6,] 0
## [7,] 0
## [8,] 0
## [9,] 0
## [10,] 0
– 但你要注意你對圖像做過的所有前處理,在預測的時候全部都要重複一次!